package cn.anecansaitin.whimsy.test.explayer;

import cn.anecansaitin.whimsy.Whimsy;
import cn.anecansaitin.whimsy.common.capabilities.battle.IBattleMode;
import cn.anecansaitin.whimsy.test.explayer.layer.BlueHand;
import cn.anecansaitin.whimsy.test.explayer.layer.PlayerLayer;
import cn.anecansaitin.whimsy.test.explayer.layer.PlayerLayerModel;
import cn.anecansaitin.whimsy.test.explayer.layer.PlayerLayerRender;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.PlayerSkin;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.player.PlayerModelPart;
import net.minecraft.world.phys.Vec3;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.Mod;
import net.neoforged.neoforge.client.event.RenderPlayerEvent;
import org.joml.Matrix4f;
import software.bernie.geckolib.cache.object.BakedGeoModel;
import software.bernie.geckolib.cache.object.GeoBone;
import software.bernie.geckolib.cache.texture.AnimatableTexture;
import software.bernie.geckolib.constant.DataTickets;
import software.bernie.geckolib.core.animatable.GeoAnimatable;
import software.bernie.geckolib.core.animation.AnimationState;
import software.bernie.geckolib.core.object.Color;
import software.bernie.geckolib.model.GeoModel;
import software.bernie.geckolib.model.data.EntityModelData;
import software.bernie.geckolib.renderer.GeoRenderer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayer;
import software.bernie.geckolib.renderer.layer.GeoRenderLayersContainer;
import software.bernie.geckolib.util.RenderUtils;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;

@Mod.EventBusSubscriber(modid = Whimsy.MODID, value = Dist.CLIENT)
public class GeoPlayerRenderer implements GeoRenderer<GeoPlayer> {
    @SubscribeEvent
    public static void onPlayerRender(RenderPlayerEvent.Pre event) {
        if (event.getEntity().getCapability(IBattleMode.BATTLE_MODE).isInMode()) {
            event.setCanceled(true);
            INSTANCE.render((LocalPlayer) event.getEntity(), event.getPoseStack(), event.getMultiBufferSource(), event.getPackedLight());
        }
    }

    public static final GeoPlayerRenderer INSTANCE = new GeoPlayerRenderer();

    private final List<GeoPlayerModel> playerModels = new ArrayList<>();
    private final GeoPlayer geoPlayer = GeoPlayer.INSTANCE;

    protected final GeoRenderLayersContainer<GeoPlayer> renderLayers = new GeoRenderLayersContainer<>(this);
    protected float scaleWidth = 0.94f;
    protected float scaleHeight = 0.94f;

    protected Matrix4f objectRenderTranslations = new Matrix4f();
    protected Matrix4f modelRenderTranslations = new Matrix4f();

    private GeoPlayerRenderer() {
        playerModels.add(new GeoPlayerModel(Whimsy.resource("slim_player"), Whimsy.resource("player_test")));
        playerModels.add(new GeoPlayerModel(Whimsy.resource("wide_player"), Whimsy.resource("player_test")));
        addRenderLayer(new PlayerLayer<>(this, new PlayerLayerRender<GeoAnimatable>(new PlayerLayerModel<>(Whimsy.resource("blue_hand")), BlueHand.INSTANCE), "RightArmUp"));
    }

    @Override
    public GeoModel<GeoPlayer> getGeoModel() {
        return playerModels.get(geoPlayer.getCurrentPlayer().getSkin().model() == PlayerSkin.Model.SLIM ? 0 : 1);
    }

    public void render(LocalPlayer player, PoseStack poseStack, @Nullable MultiBufferSource bufferSource, int packedLight) {
        geoPlayer.setCurrentPlayer(player);
        //todo 如果在大招期间，需要半透明，则传入这个渲染类型。
        RenderType renderType = RenderType.itemEntityTranslucentCull(getTextureLocation(geoPlayer));
        VertexConsumer buffer = bufferSource.getBuffer(renderType);
        render(poseStack, geoPlayer, bufferSource, renderType, buffer, packedLight);
    }

    private Color color = new Color(0x7fffffff);
    @Override
    public Color getRenderColor(GeoPlayer animatable, float partialTick, int packedLight) {
        return color;
    }

    @Override
    public long getInstanceId(GeoPlayer geoPlayer) {
        return geoPlayer.getCurrentPlayer().getId();
    }

    @Override
    public GeoPlayer getAnimatable() {
        return geoPlayer;
    }

    @Override
    public List<GeoRenderLayer<GeoPlayer>> getRenderLayers() {
        return renderLayers.getRenderLayers();
    }

    public GeoPlayerRenderer addRenderLayer(GeoRenderLayer<GeoPlayer> renderLayer) {
        renderLayers.addLayer(renderLayer);
        return this;
    }

    public GeoPlayerRenderer withScale(float scale) {
        return withScale(scale, scale);
    }

    public GeoPlayerRenderer withScale(float scaleWidth, float scaleHeight) {
        this.scaleWidth = scaleWidth;
        this.scaleHeight = scaleHeight;
        return this;
    }

    public void render(PoseStack poseStack, GeoPlayer ignore, @Nullable MultiBufferSource bufferSource, @Nullable RenderType renderType, @Nullable VertexConsumer buffer, int packedLight) {
        Minecraft mc = Minecraft.getInstance();

        if (buffer == null) bufferSource = mc.levelRenderer.renderBuffers.bufferSource();

        defaultRender(poseStack, geoPlayer, bufferSource, renderType, buffer, 0, mc.getFrameTime(), packedLight);
    }

    @Override
    public void preRender(PoseStack poseStack, GeoPlayer ignore, BakedGeoModel model, MultiBufferSource bufferSource, VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {
        objectRenderTranslations = new Matrix4f(poseStack.last().pose());

        scaleModelForRender(this.scaleWidth, this.scaleHeight, poseStack, geoPlayer, model, isReRender, partialTick, packedLight, packedOverlay);
    }

    @Override
    public void actuallyRender(PoseStack poseStack, GeoPlayer ignore, BakedGeoModel model, RenderType renderType, MultiBufferSource bufferSource, VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {
        poseStack.pushPose();
        Player player = geoPlayer.getCurrentPlayer();
        //额外开始
        boolean shouldSit = player.isPassenger() && (player.getVehicle() != null && player.getVehicle().shouldRiderSit());
        float lerpBodyRot = Mth.rotLerp(partialTick, player.yBodyRotO, player.yBodyRot);
        float lerpHeadRot = Mth.rotLerp(partialTick, player.yHeadRotO, player.yHeadRot);
        float netHeadYaw = lerpHeadRot - lerpBodyRot;

        if (shouldSit && player.getVehicle() instanceof LivingEntity livingentity) {
            lerpBodyRot = Mth.rotLerp(partialTick, livingentity.yBodyRotO, livingentity.yBodyRot);
            netHeadYaw = lerpHeadRot - lerpBodyRot;
            float clampedHeadYaw = Mth.clamp(Mth.wrapDegrees(netHeadYaw), -85, 85);
            lerpBodyRot = lerpHeadRot - clampedHeadYaw;

            if (clampedHeadYaw * clampedHeadYaw > 2500f)
                lerpBodyRot += clampedHeadYaw * 0.2f;

            netHeadYaw = lerpHeadRot - lerpBodyRot;
        }

        if (player.getPose() == Pose.SLEEPING) {
            Direction bedDirection = player.getBedOrientation();

            if (bedDirection != null) {
                float eyePosOffset = player.getEyeHeight(Pose.STANDING) - 0.1F;

                poseStack.translate(-bedDirection.getStepX() * eyePosOffset, 0, -bedDirection.getStepZ() * eyePosOffset);
            }
        }

        float limbSwingAmount = 0;
        float limbSwing = 0;

        applyRotations(poseStack, lerpBodyRot, partialTick);

        if (!shouldSit && player.isAlive()) {
            limbSwingAmount = player.walkAnimation.speed(partialTick);
            limbSwing = player.walkAnimation.position(partialTick);

            if (player.isBaby())
                limbSwing *= 3f;

            if (limbSwingAmount > 1f)
                limbSwingAmount = 1f;
        }

        float headPitch = Mth.lerp(partialTick, player.xRotO, player.getXRot());
        float motionThreshold = getMotionAnimThreshold(geoPlayer);
        boolean isMoving;

        Vec3 velocity = player.getDeltaMovement();
        float avgVelocity = (float) (Math.abs(velocity.x) + Math.abs(velocity.z)) / 2f;

        isMoving = avgVelocity >= motionThreshold && limbSwingAmount != 0;

        if (!isReRender) {
            AnimationState<GeoPlayer> animationState = new AnimationState<>(geoPlayer, limbSwing, limbSwingAmount, partialTick, isMoving);
            long instanceId = getInstanceId(geoPlayer);

            animationState.setData(DataTickets.ENTITY_MODEL_DATA, new EntityModelData(shouldSit, false, -netHeadYaw, -headPitch));
            getGeoModel().addAdditionalStateData(geoPlayer, instanceId, animationState::setData);
            getGeoModel().handleAnimations(geoPlayer, instanceId, animationState);
        }

        modelRenderTranslations = new Matrix4f(poseStack.last().pose());

        if (player.isInvisibleTo(Minecraft.getInstance().player)) {
            if (Minecraft.getInstance().shouldEntityAppearGlowing(player)) {
                buffer = bufferSource.getBuffer(renderType = RenderType.outline(getTextureLocation(geoPlayer)));
            } else {
                renderType = null;
            }
        }

        GeoRenderer.super.actuallyRender(poseStack, geoPlayer, model, renderType, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, red, green, blue, alpha);
        poseStack.popPose();
    }

    @Override
    public void renderRecursively(PoseStack poseStack, GeoPlayer ignore, GeoBone bone, RenderType renderType, MultiBufferSource bufferSource, VertexConsumer buffer, boolean isReRender, float partialTick, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {
        if (bone.isTrackingMatrices()) {
            Matrix4f poseState = new Matrix4f(poseStack.last().pose());

            bone.setModelSpaceMatrix(RenderUtils.invertAndMultiplyMatrices(poseState, this.modelRenderTranslations));
            bone.setLocalSpaceMatrix(RenderUtils.invertAndMultiplyMatrices(poseState, this.objectRenderTranslations));
        }

        GeoRenderer.super.renderRecursively(poseStack, geoPlayer, bone, renderType, bufferSource, buffer, isReRender, partialTick, packedLight, packedOverlay, red, green, blue, alpha);
    }

    @Override
    public void updateAnimatedTextureFrame(GeoPlayer ignore) {
        AnimatableTexture.setAndUpdate(getTextureLocation(geoPlayer));
    }

    @Override
    public void fireCompileRenderLayersEvent() {
    }

    @Override
    public boolean firePreRenderEvent(PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) {
        return true;
    }

    @Override
    public void firePostRenderEvent(PoseStack poseStack, BakedGeoModel model, MultiBufferSource bufferSource, float partialTick, int packedLight) {
    }

    protected void applyRotations(PoseStack poseStack, float rotationYaw, float partialTick) {
        LocalPlayer player = geoPlayer.getCurrentPlayer();
        Pose pose = player.getPose();

        if (isShaking()) {
            rotationYaw += (float) (Math.cos(player.tickCount * 3.25d) * Math.PI * 0.4d);
        }

        if (pose != Pose.SLEEPING) {
            poseStack.mulPose(Axis.YP.rotationDegrees(180f - rotationYaw));
        }

        if (player.deathTime > 0) {
            float deathRotation = (player.deathTime + partialTick - 1f) / 20f * 1.6f;

            poseStack.mulPose(Axis.ZP.rotationDegrees(Math.min(Mth.sqrt(deathRotation), 1) * getDeathMaxRotation()));
        } else if (player.isAutoSpinAttack()) {
            poseStack.mulPose(Axis.XP.rotationDegrees(-90f - player.getXRot()));
            poseStack.mulPose(Axis.YP.rotationDegrees((player.tickCount + partialTick) * -75f));
        } else if (pose == Pose.SLEEPING) {
            Direction bedOrientation = player.getBedOrientation();

            poseStack.mulPose(Axis.YP.rotationDegrees(bedOrientation != null ? RenderUtils.getDirectionAngle(bedOrientation) : rotationYaw));
            poseStack.mulPose(Axis.ZP.rotationDegrees(getDeathMaxRotation()));
            poseStack.mulPose(Axis.YP.rotationDegrees(270f));
        } else {
            player.hasCustomName();
            String name = player.getName().getString();

            if (!player.isModelPartShown(PlayerModelPart.CAPE)) {
                return;
            }

            if (name.equals("Dinnerbone") || name.equalsIgnoreCase("Grumm")) {
                poseStack.translate(0, player.getBbHeight() + 0.1f, 0);
                poseStack.mulPose(Axis.ZP.rotationDegrees(180f));
            }
        }
    }

    public boolean isShaking() {
        return geoPlayer.getCurrentPlayer().isFullyFrozen();
    }

    protected float getDeathMaxRotation() {
        return 90f;
    }
}
